
//
// 130321 - AH - Created.
// 130323 - AH - current property.
//

package org.japril.swing.jutil.logging;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.japril.jutil.IOUtil;
import org.japril.jutil.logging.ControlLogSink;
import org.japril.jutil.logging.Log;

/**
 * <p>
 *   Singleton that manages 
 *   {@link ControlLogSink}s.
 * </p>
 * @author Arne Halvorsen (AH)
 */
public final class ControlLogSinks 
  implements Iterable<ControlLogSinks.ControlLogSinkManager>
{
  private static ControlLogSinks c_instance=null;
  
  private transient List<ChangeListener> m_listeners=null;
  
  private final File m_dir;
  
  /**
   * <p>
   *   Manages a 
   *   {@link ControlLogSink}.
   * </p>
   * @author Arne Halvorsen (AH)
   */
  public static final class ControlLogSinkManager
  {
    private final File m_xmlFile;
    private final ControlLogSink m_sink;
    
    private ControlLogSinkManager(File xmlFile, ControlLogSink sink)
    {
      m_xmlFile=xmlFile;
      m_sink=sink;
    }
    
    private ControlLogSinkManager(File dir, String name) throws IOException
    {
      m_sink=new ControlLogSink(name);
      m_xmlFile=IOUtil.getNewFile(dir, "lconf", ".jlxml");
      save();
    }
    
    /**
     * <p>
     *   Gets 
     *   {@link ControlLogSink} {@code this} manages.
     * </p>
     * @return Sink.
     */
    public ControlLogSink getSink(){ return m_sink; }
    
    /**
     * <p>
     *   Gets name of sink.
     * </p>
     * @return Name.
     */
    public String getName(){ return m_sink.getName(); }
    
    /**
     * <p>
     *   Saves the state of the managed 
     *   {@link ControlLogSink}.
     * </p>
     * @throws IOException If fails.
     */
    public void save() throws IOException 
    { 
      IOUtil.save(m_xmlFile, m_sink.toXml()); 
    }
    
    /**
     * <p>
     *   Delete file associated with this, us TODO 
     * </p>
     */
    private void delete(){ m_xmlFile.delete(); }
    
    @Override
    public String toString(){ return getName(); }
    
  }
  
  private ControlLogSinkManager m_current=null;
  
  private final List<ControlLogSinkManager> m_content=
    new ArrayList<ControlLogSinkManager>();
  
  /**
   * <p>
   *   Sets current
   *   {@link ControlLogSinkManager}.
   * </p>
   * @param clsm The one to set, if {@code null} sets default
   */
  public void setCurrent(ControlLogSinkManager clsm)
  {
    if (clsm==null)
    {
      setDefault();
    }
    else
    {
      int idx=indexOf(clsm);
      if (idx==-1)
      {
        throw new IllegalArgumentException("uknown manager");
      }
      
      if (m_current!=clsm)
      {
        m_current=clsm;
        updateLogSink();
        fire();
      }
    }
  }
  
  /**
   * <p>
   *   Sets current
   *   {@link ControlLogSinkManager} given the
   *   {@link ControlLogSink} it manages.
   * </p>
   * @param clsm The one to set, if {@code null} sets default
   */
  public void setCurrent(ControlLogSink cls)
  {
    if (cls==null)
    {
      setDefault();
    }
    else
    {
      int idx=indexOf(cls);
      if (idx==-1)
      {
        throw new IllegalArgumentException("uknown manager");
      }
      
      ControlLogSinkManager clsm=get(idx);
      setCurrent(clsm);
    }
  }
  
  private void updateLogSink()
  {
    if (m_current!=null)
    {
      Log.setLogSink(m_current.getSink());
    }
  }
  
  /**
   * <p>
   *   Sets default
   *   {@link ControlLogSinkManager}.
   * </p>
   */
  public void setDefault()
  {
    ControlLogSinkManager curr=m_current;
    if (!m_content.isEmpty())
    {
      m_current=m_content.get(0);
    }
    else
    {
      try
      {
        m_current=new ControlLogSinkManager(m_dir, "unnamed");
      }
      catch (IOException iox)
      {
        iox.printStackTrace();
      }
    }
    
    if (curr!=m_current) 
    {
      updateLogSink();
      fire();
    }
  }
  
  /**
   * <p>
   *   Gets current
   *   {@link ControlLogSinkManager}.
   * </p>
   * @return Current or {@code null} if {@code this} is empty.
   */
  public ControlLogSinkManager getCurrent()
  {
    if (m_current==null) setDefault();
    return m_current;
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link ControlLogSink} of current
   *   {@link ControlLogSinkManager}.
   * </p>
   * @return Current or {@code null} if {@code this} is empty.
   */
  public ControlLogSink getCurrentSink()
  {
    ControlLogSinkManager m=getCurrent();
    return m==null ? null : m.getSink();
  }
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @throws IOException If fails to restore from file system.
   */
  private ControlLogSinks() throws IOException
  {
    m_dir=new File("./jalogging");
    if (m_dir.exists())
    {
      if (!m_dir.isDirectory())
      {
        throw new IOException("not a directory: "+m_dir);
      }
    }
    else
    {
      if (!m_dir.mkdirs())
      {
        throw new IOException("failed to create directory: "+m_dir);
      }
    }
    
    File[] files=m_dir.listFiles();
    for (File file : files)
    {
      if (file.isFile())
      {
        String name=file.getName();
        int idx=name.lastIndexOf('.');
        if (idx!=-1)
        {
          String ext=name.substring(idx+1);
          if (ext.equals("jlxml"))
          {
            ControlLogSink cls=ControlLogSink.parse(file);
            m_content.add(new ControlLogSinkManager(file, cls));
          }
        }
      }
    }
    
    File pfile=new File(m_dir, "logging.properties");
    if (pfile.exists())
    {
      InputStream is=null;
      try
      {
        is=new FileInputStream(pfile);  
    
        Properties properties=new Properties();
        properties.load(is);
        
        String current=properties.getProperty("current");
        if (current!=null)
        {
          ControlLogSinkManager clsm=get(current);
          if (clsm!=null) 
          {
            System.out.println("ControlLogSinks: sets current: "+
              clsm.getName());
            setCurrent(clsm);
          }
          else
          {
            System.out.println("ControlLogSinks: did not find current: "+
              current);
          }
        }
        else
        {
          System.out.println("ControlLogSinks: no current project");
        }
      }
      finally
      {
        if (is!=null) try { is.close(); } catch (IOException _){}
      }
    }
  }
  
  /**
   * <p>
   *   Gets instance to use.
   * </p>
   * @return Instance If fails to get instance to use.
   */
  public static synchronized ControlLogSinks getInstance()
  {
    if (c_instance==null) 
    {
      try
      {
        c_instance=new ControlLogSinks();
      }
      catch (IOException iox)
      {
        iox.printStackTrace();
      }
    }
    return c_instance;
  }
  
  /**
   * <p>
   *   Saves to files.
   * </p>
   * @throws IOException If fails.
   */
  public void save() throws IOException
  {
    for (ControlLogSinkManager sm : m_content) sm.save();
    savePropertiesFile();
  }
  
  private void savePropertiesFile() throws IOException
  {
    File f=new File(m_dir, "logging.properties");
    FileOutputStream os=null;
    try
    {
      os=new FileOutputStream(f);  
    
      Properties props=new Properties();
      if (m_current!=null)
      {
        props.setProperty("current", m_current.getName());
      }
      props.store(os, "J/April Logging");
    }
    finally
    {
      if (os!=null) try { os.close(); } catch (IOException _){}
    }
  }
  
  /**
   * <p>
   *   Gets number of 
   *   {@link ControlLogSink}s managed.
   * </p>
   * @return Count.
   */
  public int size(){ return m_content.size(); }
  
  /**
   * <p>
   *   Gets 
   *   {@link ControlLogSinkManager}.
   * </p>
   * @param idx Index of element to get.
   * @return {@code ControlSinkManager}.
   * @throws IndexOutOfBoundsException If {@code idx<0 || idx>=size()}.
   */
  public ControlLogSinkManager get(int idx){ return m_content.get(idx); }
  
  /**
   * <p>
   *   Tells if names
   *   {@link ControlLogSink} managed by {@code this}.
   * </p>
   * @param name Name.
   * @return {@code true} if is else {@code false}.
   */
  public boolean exists(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    for (ControlLogSinkManager curr : m_content)
    {
      if (curr.getName().equals(name)) return true; 
    }
    
    return false;
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link ControlLogSinkManager} that control named
   *   {@link ControlLogSink}.
   * </p>
   * @param name Name.
   * @return Found or {@code null} if non found.
   */
  public ControlLogSinkManager get(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    for (ControlLogSinkManager curr : m_content)
    {
      if (curr.getName().equals(name)) return curr; 
    }
    
    return null;
  }
  
  /**
   * <p>
   *   Gets index of
   *   {@link ControlLogSinkManager}.
   * </p>
   * @param m {@link ControlLogSinkManager}.
   * @return Index or {@code -1} if not found.
   */
  public int indexOf(ControlLogSinkManager m)
  {
    if (m==null)
    {
      throw new NullPointerException("m");
    }
    
    return m_content.indexOf(m);
  }
  
  /**
   * <p>
   *   Gets index of 
   *   {@link ControlLogSinkManager} that manages given
   *   {@link ControlLogSink}.
   * </p>
   * @param sink {@code ControlLogSink}.
   * @return Index or {@code -1} if not found.
   */
  public int indexOf(ControlLogSink sink)
  {
    if (sink==null)
    {
      throw new NullPointerException("sink");
    }
    
    int n=m_content.size();
    for (int i=0; i<n; i++) if (m_content.get(i).getSink()==sink) return i;
    return -1;
  }
  
  /**
   * <p>
   *   Creates a new
   *   {@link ControlLogSink} and a
   *   {@link ControlLogSinkManager} that manages it. 
   * </p>
   * @param name Name on 
   *             {@link ControlLogSink} to create.
   * @return Manager for created.
   * @throws IOException If fails.
   * @throws IllegalArgumentException If named exists.
   */
  public ControlLogSinkManager create(String name) throws IOException
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    if (get(name)!=null)
    {
      throw new IllegalArgumentException("exists");
    }
    
    ControlLogSinkManager retVal=new ControlLogSinkManager(m_dir, name);
    m_content.add(retVal);
    
    fire();
    
    return retVal;
  }
  
  /**
   * <p>
   *   Delete
   *   {@link ControlLogSink}.
   * </p>
   * @param sink {@link ControlLogSink} to delete.
   * @return {@code true} if deleted, {@code false} if not found.  
   */
  public boolean delete(ControlLogSink sink)
  {
    if (sink==null)
    {
      throw new NullPointerException("sink");
    }
    
    int idx=indexOf(sink);
    if (idx>-1)
    {
      ControlLogSinkManager m=m_content.get(idx);
      m.delete();
      m_content.remove(idx);
      fire();
      return true;
    }
    
    return false;
  }
  
  /**
   * <p>
   *   Delete
   *   {@link ControlLogSinkManager}.
   * </p>
   * @param sink {@link ControlLogSinkManager} to delete.
   * @return {@code true} if deleted, {@code false} if not found.  
   */
  public boolean delete(ControlLogSinkManager m)
  {
    if (m==null)
    {
      throw new NullPointerException("m");
    }
    
    int idx=m_content.indexOf(m);
    if (idx>-1)
    {
      m.delete();
      m_content.remove(idx);
      fire();
      return true;
    }
    
    return false;
  }
  
  @Override
  public Iterator<ControlLogSinkManager> iterator()
  { 
    return m_content.iterator(); 
  }

  /**
   * <p>
   *   Adds
   *   {@link ChangeListener}.
   * </p>
   * @param l {@link ChangeListener}.
   */
  public synchronized void addChangeListener(ChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) m_listeners=new ArrayList<ChangeListener>();
    if (!m_listeners.contains(l)) m_listeners.add(l);
  }
  
  /**
   * <p>
   *   Removes
   *   {@link ChangeListener}.
   * </p>
   * @param l {@link ChangeListener}.
   */
  public synchronized void removeChangeListener(ChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) m_listeners=new ArrayList<ChangeListener>();
    if (!m_listeners.contains(l)) m_listeners.add(l);
  }
  
  private final ChangeEvent m_event=new ChangeEvent(this);
  
  private final static ChangeListener[] EMPTY_L_ARRAY=new ChangeListener[0];
  
  private void fire()
  {
    if (m_listeners==null) return;
    
    ChangeListener[] l=EMPTY_L_ARRAY;
    synchronized(this){ l=m_listeners.toArray(l); }
    for (ChangeListener curr : l) curr.stateChanged(m_event);
  }
  
}
